/*
* The MIT License (MIT)
*
* FXGL - JavaFX Game Library
*
* Copyright (c) 2015-2017 AlmasB (almaslvl@gmail.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.almasb.fxgl.entity;
import com.almasb.fxgl.app.FXGL;
import com.almasb.fxgl.core.math.Vec2;
import com.almasb.fxgl.ecs.Component;
import com.almasb.fxgl.ecs.Control;
import com.almasb.fxgl.ecs.Entity;
import com.almasb.fxgl.entity.animation.AnimationBuilder;
import com.almasb.fxgl.entity.component.*;
import com.almasb.fxgl.parser.tiled.Layer;
import com.almasb.fxgl.parser.tiled.TiledMap;
import com.almasb.fxgl.parser.tiled.Tileset;
import com.almasb.fxgl.physics.BoundingShape;
import com.almasb.fxgl.physics.HitBox;
import com.almasb.fxgl.physics.PhysicsComponent;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import java.util.List;
/**
* Helper class with static convenience methods.
*
* @author Almas Baimagambetov (AlmasB) (almaslvl@gmail.com)
*/
public final class Entities {
private Entities() {}
/**
* Convenient way to obtain position component.
*
* @param e entity
* @return position component
*/
public static PositionComponent getPosition(Entity e) {
return e.getComponentUnsafe(PositionComponent.class);
}
/**
* Convenient way to obtain rotation component.
*
* @param e entity
* @return rotation component
*/
public static RotationComponent getRotation(Entity e) {
return e.getComponentUnsafe(RotationComponent.class);
}
/**
* Convenient way to obtain bbox component.
*
* @param e entity
* @return bbox component
*/
public static BoundingBoxComponent getBBox(Entity e) {
return e.getComponentUnsafe(BoundingBoxComponent.class);
}
/**
* Convenient way to obtain physics component.
*
* @param e entity
* @return physics component
*/
public static PhysicsComponent getPhysics(Entity e) {
return e.getComponentUnsafe(PhysicsComponent.class);
}
/**
* Convenient way to obtain main view component.
*
* @param e entity
* @return main view component
*/
public static ViewComponent getView(Entity e) {
return e.getComponentUnsafe(ViewComponent.class);
}
/**
* Convenient way to obtain type component.
*
* @param e entity
* @return type component
*/
public static TypeComponent getType(Entity e) {
return e.getComponentUnsafe(TypeComponent.class);
}
/**
* Create an entity with bounding box around the screen with given thickness.
*
* @param thickness thickness of hit boxes around the screen
* @return entity with screen bounds
*/
public static Entity makeScreenBounds(double thickness) {
double w = FXGL.getSettings().getWidth();
double h = FXGL.getSettings().getHeight();
Entity bounds = new Entity();
bounds.addComponent(new PositionComponent(0, 0));
bounds.addComponent(new RotationComponent(0));
bounds.addComponent(new BoundingBoxComponent(
new HitBox("LEFT", new Point2D(-thickness, 0), BoundingShape.box(thickness, h)),
new HitBox("RIGHT", new Point2D(w, 0), BoundingShape.box(thickness, h)),
new HitBox("TOP", new Point2D(0, -thickness), BoundingShape.box(w, thickness)),
new HitBox("BOT", new Point2D(0, h), BoundingShape.box(w, thickness))
));
bounds.addComponent(new PhysicsComponent());
return bounds;
}
/**
* @return new entity builder
*/
public static GameEntityBuilder builder() {
return new GameEntityBuilder();
}
/**
* @return new animation builder
*/
public static AnimationBuilder animationBuilder() {
return new AnimationBuilder();
}
/**
* Provides fluent API for building entities.
*/
public static class GameEntityBuilder {
private GameEntity entity = new GameEntity();
public GameEntityBuilder from(SpawnData data) {
at(data.getX(), data.getY());
return this;
}
public GameEntityBuilder type(Enum<?> type) {
entity.getTypeComponent().setValue(type);
return this;
}
public GameEntityBuilder at(double x, double y) {
entity.getPositionComponent().setValue(x, y);
return this;
}
public GameEntityBuilder at(Point2D position) {
return at(position.getX(), position.getY());
}
public GameEntityBuilder at(Vec2 position) {
return at(position.x, position.y);
}
public GameEntityBuilder rotate(double angle) {
entity.getRotationComponent().setValue(angle);
return this;
}
public GameEntityBuilder bbox(HitBox box) {
entity.getBoundingBoxComponent().addHitBox(box);
return this;
}
public GameEntityBuilder viewFromNode(Node view) {
entity.getViewComponent().setView(view);
return this;
}
public GameEntityBuilder viewFromNodeWithBBox(Node view) {
entity.getViewComponent().setView(view, true);
return this;
}
public GameEntityBuilder viewFromTexture(String textureName) {
entity.getViewComponent().setTexture(textureName);
return this;
}
public GameEntityBuilder viewFromTextureWithBBox(String textureName) {
entity.getViewComponent().setTexture(textureName, true);
return this;
}
public GameEntityBuilder renderLayer(RenderLayer layer) {
entity.getViewComponent().setRenderLayer(layer);
return this;
}
/**
* Generates view from tiles with {@link RenderLayer#TOP}.
*
* @param map parsed Tiled map
* @param layerName layer name as specified by Tiled
* @return builder
*/
public GameEntityBuilder viewFromTiles(TiledMap map, String layerName) {
return viewFromTiles(map, layerName, RenderLayer.TOP);
}
/**
* Generates view from tiles.
*
* @param map parsed Tiled map
* @param layerName layer name as specified by Tiled
* @param renderLayer created view will use this render layer
* @return builder
*/
public GameEntityBuilder viewFromTiles(TiledMap map, String layerName, RenderLayer renderLayer) {
entity.getViewComponent().setView(tilesToView(map, layerName), false);
entity.getViewComponent().setRenderLayer(renderLayer);
return this;
}
private Node tilesToView(TiledMap map, String layerName) {
Layer layer = map.getLayerByName(layerName);
WritableImage buffer = new WritableImage(
layer.getWidth() * map.getTilewidth(),
layer.getHeight() * map.getTileheight()
);
for (int i = 0; i < layer.getData().size(); i++) {
int gid = layer.getData().get(i);
// empty tile
if (gid == 0)
continue;
Tileset tileset = findTileset(gid, map.getTilesets());
// we offset because data is encoded as continuous
gid -= tileset.getFirstgid();
// image source
int tilex = gid % tileset.getColumns();
int tiley = gid / tileset.getColumns();
// image destination
int x = i % layer.getWidth();
int y = i / layer.getWidth();
int w = tileset.getTilewidth();
int h = tileset.getTileheight();
String imageName = tileset.getImage();
imageName = imageName.substring(imageName.lastIndexOf("/") + 1);
Image sourceImage = FXGL.getAssetLoader().loadTexture(imageName).getImage();
buffer.getPixelWriter().setPixels(x * w, y * h,
w, h, sourceImage.getPixelReader(), tilex * w, tiley * h);
}
return new ImageView(buffer);
}
public GameEntityBuilder with(Component... components) {
for (Component c : components)
entity.addComponent(c);
return this;
}
public GameEntityBuilder with(Control... controls) {
for (Control c : controls)
entity.addControl(c);
return this;
}
/**
* Finishes building entity.
*
* @return entity
*/
public GameEntity build() {
return entity;
}
/**
* Finishes building the entity and attaches it to given world.
*
* @param world the world to attach entity to
* @return entity
*/
public GameEntity buildAndAttach(GameWorld world) {
world.addEntity(entity);
return entity;
}
}
/**
* Finds tileset where gid is located.
*
* @param gid tile id
* @param tilesets all tilesets
* @return tileset
*/
private static Tileset findTileset(int gid, List<Tileset> tilesets) {
return tilesets.stream()
.filter(tileset ->
gid >= tileset.getFirstgid() && gid < tileset.getFirstgid() + tileset.getTilecount())
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Tileset for gid=" + gid + " not found"));
}
}